/*
 * kdogs - Kick dogs, a lightweight watchdog kicker
 *
 * Copyright (C) 2007 Instituto Nokia de Tecnologia
 *
 * Author: Osvaldo Santana Neto <osvaldo.neto@openbossa.org>
 *         Abner Silva <abner.silva@openbossa.org>
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation version 2 of the License.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <signal.h>

#include <sys/socket.h>
#include <sys/un.h>
#include <sys/resource.h>

#include "kdogsd.h"
#include "kdogs_ioctl.h"
#include "kdogs_sysfs.h"
#include "kdogs_cmd.h"
#include "kdogs_settings.h"

static int keepalive = 1;
static int kdogsd_sock = -1;
static int kick_timeout = KDOGSD_DEFAULT_INTERVAL;

static void
usage (void)
{
    fprintf (stderr, "\n" "Usage: kdogsd [--help] [--version] [--timeout=n] [--daemon]\n");
    fprintf (stderr,
             "\n"
             "           --daemon        Become a daemon\n"
             "           --timeout=n     Description here\n"
             "           --version       Print version information and exit\n"
             "           --help          Show this help and exit\n"
             "\n" "For more information visit http://dev.openbossa.org/trac/mamona\n" "\n");
}

/* signal handler */
static void
sig_handler (int num)
{
    if ((num == SIGINT) || (num == SIGTERM))
        keepalive = 0;
}

/* common */
int
init_wdts (void)
{
    int i;
    int ret = 0;
    kdogs_wdt *wdt;

#ifdef TEST
    printf ("initializing wdt\n");
    return 0;
#endif

    for (i = 0; i < WDTS_SIZE; ++i) {
        wdt = WDT (i);

        switch (wdt->type) {
        case WDT_CTRL_IOCTL:
            if (_ioctl_init_wdt (wdt) < 0)
                ret = -1;
            break;
        case WDT_CTRL_SYSFS:
            if (_sysfs_init_wdt (wdt) < 0)
                ret = -1;
            break;
        }
    }
    return ret;
}

int
close_wdts (void)
{
    int i;
    int ret = 0;
    kdogs_wdt *wdt;

#ifdef TEST
    printf ("closing wdt\n");
    return 0;
#endif

    for (i = 0; i < WDTS_SIZE; ++i) {
        wdt = WDT (i);

        switch (wdt->type) {
        case WDT_CTRL_IOCTL:
            if (_ioctl_close_wdt (wdt) < 0)
                ret = -1;
            break;
        case WDT_CTRL_SYSFS:
            if (_sysfs_close_wdt (wdt) < 0)
                ret = -1;
            break;
        }
    }
    return ret;
}

int
kick (void)
{
    int i;
    int ret = 0;
    kdogs_wdt *wdt;

#ifdef TEST
    printf ("Kicking\n");
    return 0;
#endif

    for (i = 0; i < WDTS_SIZE; i++) {
        wdt = WDT (i);

        switch (wdt->type) {
        case WDT_CTRL_IOCTL:
            if (_ioctl_kick (wdt) < 0)
                ret = -1;
            break;
        case WDT_CTRL_SYSFS:
            if (_sysfs_kick (wdt) < 0)
                ret = -1;
            break;
        }
    }
    return ret;
}

int
get_timeout (kdogs_wdt * wdt)
{
    switch (wdt->type) {
    case WDT_CTRL_IOCTL:
        return _ioctl_get_timeout (wdt);
    case WDT_CTRL_SYSFS:
        return _ioctl_get_timeout (wdt);
    }
    return -1;
}

int
set_timeout (kdogs_wdt * wdt, unsigned int timeout)
{
#ifdef TEST
    printf ("setting timeout\n");
    return 0;
#endif

    switch (wdt->type) {
    case WDT_CTRL_IOCTL:
        return _ioctl_set_timeout (wdt, timeout);
    case WDT_CTRL_SYSFS:
        return _ioctl_set_timeout (wdt, timeout);
    }
    return -1;
}

int
get_counter (kdogs_wdt * wdt)
{
    switch (wdt->type) {
    case WDT_CTRL_IOCTL:
        return _ioctl_get_counter (wdt);
    case WDT_CTRL_SYSFS:
        return _ioctl_get_counter (wdt);
    }
    return -1;
}

/* Command */
int
init_cmd (void)
{
    struct sockaddr_un saddr;
    socklen_t addrlen;

    kdogsd_sock = socket (AF_LOCAL, SOCK_DGRAM, 0);
    if (kdogsd_sock < 0) {
        errmsg ("error getting socket: %s\n", strerror (errno));
        return -1;
    }

    memset (&saddr, 0x00, sizeof (saddr));
    saddr.sun_family = AF_LOCAL;
    strcpy (&saddr.sun_path[1], KDOGSD_CTRL_SOCK_PATH);
    addrlen = offsetof (struct sockaddr_un, sun_path) + strlen (saddr.sun_path + 1) + 1;

    if (bind (kdogsd_sock, (struct sockaddr *) &saddr, addrlen) < 0) {
        errmsg ("bind failed: %s\n", strerror (errno));
        close (kdogsd_sock);
        kdogsd_sock = -1;
        return -1;
    }

    return 0;
}

void
close_cmd (void)
{
    if (kdogsd_sock > 0) {
        close(kdogsd_sock);
        kdogsd_sock = -1;
    }
}

int
wait_cmd (void)
{
    int fdcount;
    fd_set readfds;
    struct timeval tv;
    kdogs_cmd command = {0, 0};
    kdogs_wdt *wdt = 0x00;

    FD_ZERO (&readfds);
    FD_SET (kdogsd_sock, &readfds);

    tv.tv_sec = kick_timeout;
    tv.tv_usec = 0;

    fdcount = select (kdogsd_sock + 1, &readfds, 0x00, 0x00, &tv);

    if (fdcount > 0)
        if ((kdogsd_sock >= 0) && (FD_ISSET (kdogsd_sock, &readfds))) {
            int bytes = 0;

            bytes = read (kdogsd_sock, &command, sizeof(command));

            if (bytes <= 0) {
                errmsg ("error reading command: %s\n", strerror (errno));
                return -1;
            }

            switch (command.cmd) {
                int i;
                case _KDOGS_CMD_EXIT:
                    keepalive = 0;
                    break;
                case _KDOGS_CMD_SHUTDOWN:
                    for (i = 0; i < WDTS_SIZE; ++i) {
                        wdt = WDT (i);
                        set_timeout (wdt, 0);
                    }
                    break;
                case _KDOGS_CMD_CHANGE_INTERVAL:
                    if (command.value >= 0)
                        kick_timeout = command.value;
                    break;
                case _KDOGS_CMD_KICK:
                default:
                    break;
            }
        }

    return 0;
}

int
main (int argc, char *argv[])
{
    int option;
    int daemonize = 0;
    struct sigaction act;

    static const struct option options[] = {
        {"daemon", 0, 0x00, 'd'},
        {"timeout", 1, 0x00, 't'},
        {"help", 0, 0x00, 'h'},
        {"version", 0, 0x00, 'V'},
        {}
    };

    /* parse parameters */
    while (1) {
        option = getopt_long (argc, argv, "dt:hV", options, 0x00);
        if (option == -1)
            break;

        switch (option) {
        case 'd':
            daemonize = 1;
            break;
        case 't':
            kick_timeout = strtol (optarg, 0x00, 10);
            if (kick_timeout < 1) {
                errmsg ("invalid kick timeout (<1s).\n");
                return 1;
            }
            break;
        case 'h':
            usage ();
            return 0;
        case 'V':
            fprintf (stderr, "%s\n", VERSION);
            return 0;
        default:
            usage ();
            return 0;
        }
    }

#ifndef TEST
    /* check for root privileges */
    if (getuid () != 0) {
        errmsg ("root privileges required\n");
        exit(1);
    }
#endif

    /* set signal handler */
    memset (&act, 0x00, sizeof (struct sigaction));
    act.sa_handler = (void (*)(int)) sig_handler;
    sigemptyset (&act.sa_mask);
    act.sa_flags = SA_RESTART;
    sigaction (SIGINT, &act, 0x00);
    sigaction (SIGTERM, &act, 0x00);

    /* initialize socket */
    if (init_cmd () < 0) {
        if (errno == EADDRINUSE) {
            errmsg ("another daemon already running\n");
            return 1;
        } else {
            errmsg ("error initializing socket\n");
            return 2;
        }
    }

    /* daemonize */
    if (daemonize) {
        pid_t pid;

        pid = fork ();
        switch (pid) {
        case 0:
            errmsg ("daemonized fork running\n");
            break;
        case -1:
            errmsg ("fork of daemon failed: %s\n", strerror (errno));
            return 4;
        default:
            errmsg ("child [%u] running, parent exits\n", pid);
            return 0;
        }
    }

    setpriority (PRIO_PROCESS, 0, KDOGSD_DAEMON_PRIORITY);

    init_wdts ();

    while (keepalive) {
        kick ();
        wait_cmd ();
    }

    close_cmd ();
    close_wdts ();

    return 0;
}
